﻿using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reflection;
using Cubicle.Core.Interfaces;

namespace Cubicle.Core.Processors.Invoke
{
    [Export("Cubicle.Processors.Invoke.CandidateProcessor", typeof(IProcessor<InstructionRequest>))]
    [ExportMetadata("Name", "CandidateProcessor")]
    [ExportMetadata("Description", "Identifies candidate members for the instruction request")]
    public class CandidateProcessor : InstructionRequestProcessor
    {
        private readonly BindingFlags _defaultBindingFlags = BindingFlags.Public | BindingFlags.Static;

        public CandidateProcessor(): base(RequestType.AllNonSpecial)
        {
            _defaultBindingFlags = RequestType.AllNonSpecial.ToBindingFlags();
        }

        #region Overrides of Processor<InstructionRequest>

        public override InstructionRequest ConditionalProcess(InstructionRequest request)
        {
            // Check if constructor already exists
            if (request.TargetConstructor != null)
                return request;
            else if (request.TargetField != null)
            {
                request.Type(request.Type().Subtract(RequestType.Property | RequestType.Method));
                request.Type(request.Type().Subtract(request.ParameterCount > 0
                    ? RequestType.Get
                    : RequestType.Set));
            }
            else if (request.TargetProperty != null)
            {
                request.IndexParameters = request.TargetProperty.GetIndexParameters();
                var expectedCount = request.IndexParameters.Length;
                request.Type(request.Type().Subtract(RequestType.Field | RequestType.Method));
                request.Type(request.Type().Subtract(expectedCount < request.ParameterCount
                    ? RequestType.Get
                    : RequestType.Set));
            }
            else if (request.TargetMethod != null)
            {
                request.Type(request.Type().Subtract(RequestType.Field | RequestType.Property | RequestType.Get | RequestType.Set));
            }
            // Treat constructors separately
            else if (request.Type() == RequestType.Construct)
            {

                // Find candidates
                var candidates = request.TargetType.GetConstructors(_defaultBindingFlags);

                // Cache candidates
                request.Candidates = candidates;
                request.CandidateCount = candidates.Length;

                // Choose candidate if possible
                switch (request.CandidateCount)
                {
                    case 0:
                        request.Fail("No matching constructors found. TransactionId = {0}", request.Id);
                        return request;
                    case 1:
                        // Cannot identify this as a valid target until
                        // the parameter types have been compared against those provided
                        // this is done by the candidate selection processor
                        return request;
                }
            }
            // Find members, if they do not already exists
            else
            {
                var members = request.TargetType.GetMembers(_defaultBindingFlags);
                if (members.Length == 0)
                    return request;
                var memberName = request.Inst.MemberName;
                var candidates = new List<MemberInfo>();
                for (int i = 0; i < members.Length; i++)
                    if (members[i].Name == memberName)
                        candidates.Add(members[i]);

                // Cache candidates
                request.Candidates = candidates;
                request.CandidateCount = candidates.Count;

                // Choose candidate if possible
                switch (request.CandidateCount)
                {
                    case 0:
                        request.Fail("No candidates found. TransactionId = {0}", request.Id);
                        return request;
                    case 1:
                        var target = candidates[0];
                        if (target is MethodInfo)
                        {
                            // Cannot identify this as a valid target until
                            // the parameter types have been compared against those provided
                            // this is done by the candidate selection processor
                            request.Type(request.Type().Subtract(RequestType.Field | RequestType.Property | RequestType.Get | RequestType.Set));
                        }
                        else if (target is PropertyInfo)
                        {
                            request.TargetProperty = target as PropertyInfo;
                            request.IndexParameters = request.TargetProperty.GetIndexParameters();
                            var expectedCount = request.IndexParameters.Length;
                            request.Type(request.Type().Subtract(RequestType.Field | RequestType.Method));
                            request.Type(request.Type().Subtract(expectedCount < request.ParameterCount 
                                ? RequestType.Get 
                                : RequestType.Set));
                        }
                        else if (target is FieldInfo)
                        {
                            request.TargetField = target as FieldInfo;
                            request.Type(request.Type().Subtract(RequestType.Property | RequestType.Method));
                            request.Type(request.Type().Subtract(request.ParameterCount > 0 
                                ? RequestType.Get 
                                : RequestType.Set));
                        }
                        else
                        {
                            request.Fail("Unsupported candidate found. TransactionId = {0}", request.Id);
                        }
                        break;
                    default:
                        // Only methods support overloading
                        request.Type(request.Type().Subtract(RequestType.Field | RequestType.Property | RequestType.Get | RequestType.Set));
                        break;
                }
            }
            return request;
        }

        #endregion
    }
}